package StratelegoGame;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.JPanel;

/**
 * This class displays the stratego board with all of the spaces.
 * @author Jace Ferguson
 * @filename GameBoard2D.java
 *
 */
public class GameBoard2D extends JPanel implements GameBoard {
	public static final int SPACE_FORWARD = 0;
	public static final int SPACE_BACKWARD = 1;
	public static final int SPACE_RIGHT = 2;
	public static final int SPACE_LEFT = 3;

	/**
	 * Create a row panel to put spaces in.
	 * @return	JPanel
	 */
	private static JPanel getRowContainer()
	{
		JPanel row = new JPanel();
		row.setOpaque(false);
		row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
		return row;
	}
	//Spaces are stored with their value so the can be fetched later
	protected Hashtable<Integer, GameBoard2DSpace> spaces;
	protected Integer rows;
	protected Integer columns;
	protected Image origImage;
	protected Image scaledImage;
	protected Dimension spaceSize;
	protected Dimension boardSize;
	protected Color backgroundColor;
	protected GameBoard2DLogic observer;
	private GameBoard2DPiece currentlySelectedPiece;
	private Vector<GameBoard2DSpace> acceptableMoves;

	/**
	 * Constructor creates the board based on the
	 * parameters and creates the correct amount of spaces to add to it.
	 * @param rows	Integer	number of rows the board should have
	 * @param columns	Integer number of columns the board should have.
	 */
	public GameBoard2D(Integer rows, Integer columns)
	{
		super();
		this.spaces = new Hashtable<Integer, GameBoard2DSpace>();
		this.acceptableMoves = new Vector<GameBoard2DSpace>();
		this.rows = 0;
		this.columns = 0; 
		this.spaceSize = new Dimension(0, 0);
		//Set the default background in case no picture is used.
		this.backgroundColor = Color.WHITE;
		this.setBoardDimensions(rows, columns);
		//Set the default board size.
		this.setBoardSize(new Dimension(300, 300));
		this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
	}

	/**
	 * Calculate what spaces a piece can move to based on the
	 * the starting space, the direction the piece is move and
	 * the space restriction the piece has for moving in that directions.
	 * @param origin	Integer	Starting space id.
	 * @param direction	int	Direction the space is wanting to move.
	 * @param moveRestriction	Integer number of spaces the piece
	 * 									can move in that direction
	 */
	protected void calculateSpaceIds(Integer origin, int direction, Integer moveRestriction)
	{
		//Create an offset
		Integer offset = origin;
		//Create a space to store the one's we calculate
		GameBoard2DSpace space;
		Integer columnOffset = this.getColumns();
		Integer rowOffset = 1;

		/*
		 *	if the piece is moving backwards, we will want to
		 *	subtract the number of pieces to move. 
		 */		
		if(direction == GameBoard2D.SPACE_BACKWARD)
		{
			columnOffset = -1 * columnOffset;
		}

		/*
		 * If the piece is moving to the right, we 
		 * want to subtract pieces here, too.
		 */
		if(direction == GameBoard2D.SPACE_RIGHT)
		{
			rowOffset = -1 * rowOffset;
		}
		/*
		 * The method for finding pieces in the forward or backward direction is the same.
		 */
		if(direction == GameBoard2D.SPACE_FORWARD || direction == GameBoard2D.SPACE_BACKWARD)
		{
			if(moveRestriction == -1) //Piece can move in that direction any number of spaces.
			{
				//While we still have spaces on the board find another space.
				while(offset > -1 && offset < (this.getRows() * this.getColumns()))
				{
					/*
					 * 	A space in front of or behind a current space is
					 *  just the number of columns the board has away from the
					 *  origin.
					 */					
					offset = offset - columnOffset;
					space = this.getSpace(offset);
					if(space == null)	//If there isn't a space at the calculated id, break;
					{
						break;
					}
					if(!space.isEmpty())//Check to see if the space already has a piece.
					{
						GameBoard2DPiece piece = (GameBoard2DPiece) space.getPiece();
						if(piece.getType() == GameBoard2DPiece.FOE) // If the piece is a foe we still can move there.
						{
							this.acceptableMoves.add(space);
							//When you come across any piece, that is the last possible number of moves you can move.
							break;
						}
						//The piece isn't a foe so don't selected it as being an acceptable piece. 
						break;
					}
					else	//Add the space to the acceptableMoves vector 
					{
						this.acceptableMoves.add(space);
					}
				}
			}
			else	//The piece can only move a specified number of spaces.
			{
				//Move the piece in a specified direction a specified number of times.
				for(int i = 1; i <= moveRestriction; i++)
				{
					offset = origin - (i * columnOffset);
					space = this.getSpace(offset);
					if(space == null) //Make sure the calculated space exists.
					{
						break;
					}	
					if(!space.isEmpty())	//Check to see if the space is empty.
					{
						GameBoard2DPiece piece = (GameBoard2DPiece) space.getPiece();
						if(piece.getType() == GameBoard2DPiece.FOE)
						{
							this.acceptableMoves.add(space);
							break;
						}
						break;
					}
					else	//Add the space to acceptableMoves
					{
						this.acceptableMoves.add(space);
					}
				}
			}
		}
		else if(direction == GameBoard2D.SPACE_LEFT) //The move direction is left.
		{
			if(moveRestriction == -1)	//Can move uninterrupted to the left.
			{
				//We can only move left until we get to the end of the row.
				while((offset % this.getColumns()) > 0)
				{
					offset = offset - rowOffset;
					space = this.getSpace(offset);
					if(space == null)	//Check that the calculated space exists.
					{
						break;
					}
					if(!space.isEmpty())
					{
						GameBoard2DPiece piece = (GameBoard2DPiece) space.getPiece();
						if(piece.getType() == GameBoard2DPiece.FOE)
						{
							this.acceptableMoves.add(space);
							break;
						}
						break;
					}
					else
					{
						this.acceptableMoves.add(space);
					}
				}
			}
			else	//Piece can only move a set number of spaces to the left.
			{
				for(int i = 0; i < moveRestriction; i++)
				{
					offset = offset - rowOffset;
					space = this.getSpace(offset);
					if(space == null)
					{
						break;
					}
					if(!space.isEmpty())
					{
						GameBoard2DPiece piece = (GameBoard2DPiece) space.getPiece();
						if(piece.getType() == GameBoard2DPiece.FOE)
						{
							this.acceptableMoves.add(space);
							break;
						}
						break;
					}
					else
					{
						this.acceptableMoves.add(space);
					}
					if((offset % this.getColumns()) == 0)
					{
						break;
					}
				}
			}
		}
		else if(direction == GameBoard2D.SPACE_RIGHT)	//Move the piece to the right.
		{
			if(moveRestriction == -1)	//Piece can move an infinite number of spacs
			{
				//Make sure the space we are looking at is still on the row.
				while((offset % this.getColumns()) < (this.getColumns()-1))
				{
					offset = offset - rowOffset;
					space = this.getSpace(offset);
					if(space == null || !space.isEmpty())
					{
						break;
					}
					this.acceptableMoves.add(space);
				}
			}
			else	//Get spaces a finite number of spaces away from the origin
			{
				for(int i = 0; i < moveRestriction; i++)
				{
					offset = offset - rowOffset;
					space = this.getSpace(offset);
					if(space == null || !space.isEmpty())
					{
						break;
					}
					this.acceptableMoves.add(space);
					if((offset % this.getColumns()) == this.getColumns())
					{
						break;
					}
				}
			}
		}
	}

	/**
	 * Calculate the size of the space.
	 */
	private void calculateSpaceSize()
	{
		this.spaceSize.setSize(new Dimension((int)Math.floor(this.boardSize.width/this.columns), (int)Math.floor(this.boardSize.height/this.rows)));
	}

	/**
	 * Clear any acceptable move that have been
	 * calculated and highlighted.
	 */
	public void clearAcceptableMoves()
	{
		for(GameBoard2DSpace space : this.acceptableMoves)
		{
			space.setSpaceSetable(false);
			if(space.getPiece() != null)
				space.getPiece().setPieceSelectable(false);
			space.clearHighlight();
		}
		//Clear the vector
		this.acceptableMoves.clear();
	}

	/**
	 * Remove all pieces from the board.
	 */
	public void clearBoard()
	{
		Enumeration<GameBoard2DSpace> elements = this.spaces.elements();
		//Look through all spaces for friendly pieces.
		while(elements.hasMoreElements())
		{
			GameBoard2DSpace space = elements.nextElement();
			space.clearSpace();
		}
	}

	/**
	 * Clear the piece that is currently selected.
	 */
	public void clearCurrentlySelectedPiece()
	{
		if(this.currentlySelectedPiece != null)
		{
			this.currentlySelectedPiece.getSpace().clearHighlight();
			this.clearAcceptableMoves();
			this.currentlySelectedPiece = null;
		}
	}

	/**
	 * Clear the hash that has the spaces in it.
	 */
	public void clearHash()
	{
		this.spaces.clear();
	}

	/**
	 * Create a scaled image for the background.
	 */
	private void createScaledImage()
	{
		if(this.origImage != null)
		{
			this.scaledImage = this.origImage.getScaledInstance(this.boardSize.width, this.boardSize.height, Image.SCALE_SMOOTH);
		}
	}

	/**
	 * Get the JPanel that the board was created in.
	 */
	public JPanel getBoard()
	{
		return this;
	}

	/**
	 * Get the number of columns the board has.
	 * @return	Integer
	 */
	public Integer getColumns()
	{
		return this.columns;
	}

	/**
	 * Get the piece that is selected or null if
	 * no piece is selected.
	 * @return	GameBoard2DPiece | null
	 */
	public GameBoard2DPiece getCurrentlySelectedPiece()
	{
		return this.currentlySelectedPiece;
	}

	/**
	 * Calculate all of the friendly pieces still on the board.
	 * @return	Vector<GameBoard2DPiece>	friendly pieces that still exists
	 */
	public Vector<GameBoard2DPiece> getFriendlyPieces()
	{
		Vector<GameBoard2DPiece> friends = new Vector<GameBoard2DPiece>();
		Enumeration<GameBoard2DSpace> elements = this.spaces.elements();
		//Look through all spaces for friendly pieces.
		while(elements.hasMoreElements())
		{
			GameBoard2DSpace space = elements.nextElement();
			if(!space.isEmpty()) //The space has a piece on it.
			{
				GameBoard2DPiece piece = space.getPiece();
				if(piece.getType() == GameBoard2DPiece.FRIEND)
				{
					friends.add(piece);
				}
			}
		}
		return friends;
	}

	/**
	 * Get the logic observer for the board.
	 * @return	GameBoard2DLogic
	 */
	public GameBoard2DLogic getObserver()
	{
		return this.observer;
	}

	/**
	 * Get the number of rows the board has.
	 * @return	Integer	
	 */
	public Integer getRows()
	{
		return this.rows;
	}

	/**
	 * Get a space from the has based on a id number
	 * @param spaceId	Integer	
	 * @return	GameBoard2DSpace | null if space couldn't be found.
	 */
	public GameBoard2DSpace getSpace(Integer spaceId)
	{
		return this.spaces.get(spaceId);
	}

	/**
	 * Show acceptable moves for a selected piece.
	 */
	public void locateAndShowAcceptableMoves()
	{
		this.calculateSpaceIds(this.currentlySelectedPiece.getSpace().getId(), GameBoard2D.SPACE_FORWARD, this.currentlySelectedPiece.getForwardMoveRestriction());
		this.calculateSpaceIds(this.currentlySelectedPiece.getSpace().getId(), GameBoard2D.SPACE_BACKWARD, this.currentlySelectedPiece.getBackwardMoveRestriction());
		this.calculateSpaceIds(this.currentlySelectedPiece.getSpace().getId(), GameBoard2D.SPACE_RIGHT, this.currentlySelectedPiece.getRightMoveRestriction());
		this.calculateSpaceIds(this.currentlySelectedPiece.getSpace().getId(), GameBoard2D.SPACE_LEFT, this.currentlySelectedPiece.getLeftMoveRestriction());

		//Allow all acceptable moves to be setable.
		for(GameBoard2DSpace space : this.acceptableMoves)
		{
			space.setSpaceSetable(true);
			if(space.getPiece() != null)
				space.getPiece().setPieceSelectable(true);
			space.highlightSpace();
		}

	}

	/**
	 * Mark team pieces as selectable.
	 */
	public void markFriendlyPiecesSelectable()
	{
		for(GameBoard2DPiece piece : this.getFriendlyPieces())
		{
			piece.setPieceSelectable(true);
		}
	}
	
	/**
	 * Notify the logic observer of any mouseEvents 
	 * @param e	MouseEvent
	 */
	public void notifyObserver(MouseEvent e)
	{
		if(this.observer != null)
		{
			this.observer.notify(e);
		}
	}
	
	/**
	 * Paint the component with a background picture or color.
	 */
	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		Graphics gr = g.create();
		if(this.scaledImage != null)
		{
			gr.drawImage(this.scaledImage, 0, 0, this);
		}
		else
		{
			gr.setColor(this.backgroundColor);
			gr.fillRect(0, 0, this.boardSize.width, this.boardSize.height);
		}

	}
	
	/**
	 * Get whether a piece is currently selected or not.
	 * @return	boolean
	 */
	public boolean pieceSelected()
	{
		if(this.currentlySelectedPiece != null)
		{
			return true;
		}
		return false;
	}
	
	/**
	 * Clear the current board and create a new one
	 * based on the rows and columns. This will also reset space sizes.
	 */
	public void recalculateBoard()
	{
		//Remove all stored spaces.
		this.clearHash();
		//Calculate the new space size.
		this.calculateSpaceSize();
		//Remove all components from the board component.
		this.removeAll();
		//Create a scaled image.
		this.createScaledImage();
		//Get the total number of spaces.
		Integer totalSpaces = this.rows * this.columns;
		//Create a row to hold pieces.
		JPanel row = GameBoard2D.getRowContainer();
		for(int i = 0; i < totalSpaces; i++)
		{
			//Create the space.
			GameBoard2DSpace tempSpace = new GameBoard2DSpace(i, this.spaceSize);
			//Put the space in the hash.
			this.spaces.put(i, tempSpace);
			//Add the space to the row.
			row.add(tempSpace);
			//Check to see if we have filled the row yet.
			if(((i+1)%this.columns) == 0)
			{
				//Add the completed row to the board
				this.add(row);
				//Create another board.
				row = GameBoard2D.getRowContainer();
			}
		}
		//Repaint everything.
		this.repaint();
	}
	
	/**
	 * Set every space to a specified setability.
	 * @param setable	Boolean
	 */
	public void setAllSpacesSetablility(Boolean setable)
	{
		Enumeration<GameBoard2DSpace> elements = this.spaces.elements();
		while(elements.hasMoreElements())
		{
			GameBoard2DSpace space = elements.nextElement();
			space.setSpaceSetable(setable);		
		}
	}
	
	/**
	 * Reset the background color and update the board.
	 * @param color	Color
	 */
	public void setBackgroundColor(Color color)
	{
		this.backgroundColor = color;
		this.repaint();
	}
	
	/**
	 * Set the background image to a file and repaint the board.
	 * @param imageFile	String 	location of the image.
	 */
	public void setBackgroundImage(String imageFile)
	{
		try{
			//Load and store the original image.
			this.origImage = ImageIO.read(new File(imageFile));
			//Create a scaled image to match the board size
			this.createScaledImage();
		}catch(IOException e){}
		//Repaint the board to update the image.
		this.repaint();
	}
	
	/**
	 * Sets the number of rows and columns the board should have. 
	 */
	public void setBoardDimensions(Integer rows, Integer columns)
	{
		this.setRows(rows);
		this.setColumns(columns);
	}
	
	/**
	 * Set the actual board size for the board.
	 * @param size	Dimension
	 */
	private void setBoardSize(Dimension size)
	{
		this.boardSize = size;
		//Recalculate the board based on the new size.
		this.recalculateBoard();
	}
	
	/**
	 * Set the number of columns the board should have.
	 * @param columns	Integer
	 */
	private void setColumns(Integer columns)
	{
		if(columns != null)
		{
			this.columns = columns;
		}
		else
		{
			this.columns = 10;
		}
	}
	
	/**
	 * Set a piece as being currently selected.
	 * @param piece	GameBoard2DPiece
	 */
	public void setCurrentlySelectedPiece(GameBoard2DPiece piece)
	{
		if(this.currentlySelectedPiece != null) //If a piece is already selected.
		{			
			//Clear the highlight.
			this.currentlySelectedPiece.getSpace().clearHighlight();
			//Clear any acceptable moves that the piece was allowed
			this.clearAcceptableMoves();
		}
		this.currentlySelectedPiece = piece;
		piece.getSpace().highlightSpace();
		this.locateAndShowAcceptableMoves();
	}

	/**
	 * Sets the maximum size the board can take up.
	 */
	public void setMaximumSize(Dimension size)
	{
		super.setMaximumSize(size);
		//Update the board size so it can recalculate everything.
		this.setBoardSize(size);
	}
	
	/**
	 * Set the logic observer for the board.
	 * @param observer	GameBoard2DLogic
	 */
	public void setObserver(GameBoard2DLogic observer)
	{
		this.observer = observer;
	}
	
	/**
	 * Set the number of rows a board should have.
	 * @param rows	Integer
	 */
	private void setRows(Integer rows)
	{
		if(rows != null)
		{
			this.rows = rows;
		}
		else
		{
			this.rows = 10;
		}
	}
	
	/**
	 * Set a space to the specified setability based on 
	 * space id.
	 * @param spaceId	Integer	
	 * @param setable	boolean
	 */
	public void setSpaceSetable(Integer spaceId, boolean setable)
	{
		GameBoard2DSpace space = this.spaces.get(spaceId);
		if(space != null)
		{
			space.setSpaceSetable(setable);
		}
	}
}


